package org.ainlolcat.ainscience.utils;

import java.util.ArrayList;
import java.util.List;

/**
 * @author ainlolcat
 *         Date: 5/1/13
 */
//todo test
public class ArraysUtil {

    /**
     * This method can cause too many unnecessary GC work
     * @param x
     * @param y
     * @param start
     * @param end
     * @return
     */
    @Deprecated
    public static double[][] cut(double[] x, double[] y, double start, double end){
        int dotcount = Math.min(x.length, y.length);
        List<Double> lx = new ArrayList<Double>();
        List<Double> ly = new ArrayList<Double>();
        for (int i=1;i<dotcount;i++){
            if ( x[i] > start && x[i] < end ){
                lx.add(x[i]);
                ly.add(y[i]);
            }
        }
        double[][] result = new double[2][lx.size()];
        for (int i=0;i<lx.size();i++){
            result[0][i] = lx.get(i);
            result[1][i] = ly.get(i);
        }
        return result;
    }

    /**
     * This method can cause too many unnecessary GC work
     * @param x
     * @param start
     * @param end
     * @param y
     * @return
     */
    @Deprecated
    public static double[][] cut(double[] x, double start, double end, double[]... y){
        int dotcount = Math.min(x.length, y[0].length);
        List<Double> lx = new ArrayList<Double>();
        List<List<Double>> ly = new ArrayList();
        for (int i = 0;i<y.length;i++){
            ly.add(new ArrayList<Double>());
        }

        for (int i=1;i<dotcount;i++){
            if ( x[i] > start && x[i] < end ){
                lx.add(x[i]);
                for (int j = 0; j < y.length; j++){
                    ly.get(j).add(y[j][i]);
                }
            }
        }

        double[][] result = new double[1 + y.length][lx.size()];
        for (int i=0;i<lx.size();i++){
            result[0][i] = lx.get(i);
            for (int j = 0; j < y.length; j++){
                result[1+j][i] = ly.get(j).get(i);
            }
        }
        return result;
    }

    public static double getMax(double[] y){
        double max = y[0];
        for (int i=1;i<y.length;i++){
            if (y[i]>max) max = y[i];
        }
        return max;
    }

    public static double getMax(double[] y, int subSetInfo[]){
        double max = y[subSetInfo[1]];
        for (int i=subSetInfo[1];i<subSetInfo[0];i++){
            if (y[i]>max) max = y[i];
        }
        return max;
    }

    public static double[] getNormalizedDifference(double[] x, double[] y0, double[] y) {
        double differenceInt = 0;
        double y0Int = 0;

        double y0Max = ArraysUtil.getMax(y0);
        double yMax = ArraysUtil.getMax(y);

        double dx = 0;
        for (int i=1;i<x.length;i++){
            dx = x[i] - x[i-1];
            differenceInt += dx*(y0[i]/y0Max - y[i]/yMax);
            y0Int += dx*y0[i]/y0Max;
        }
        return new double[]{differenceInt, Math.abs(differenceInt/y0Int)};
    }

    public static double peakCenter(double[] x, double[] y){
        double mean = x[0];
        double yMax  = 0;
        for (int i=0;i<(x.length<y.length?x.length:y.length);i++){
            if (y[i] > yMax){
                yMax = y[i];
                mean = x[i];
            }
        }
        return mean;
    }

    public static double peakCenterSmooth(double[] x, double[] y){
        double mean = x[0];
        double yMax  = 0;
        for (int i=2;i<(x.length<y.length?x.length:y.length)-2;i++){
            double wind = (y[i-2] + y[i-1] + y[i] + y[i+1] + y[i+2])/5;
            if (wind > yMax){
                yMax = wind;
                mean = x[i];
            }
        }
        return mean;
    }

    public static double peakCenterSmooth(double[] x, double[] y, int baseLineDots, int mean){
        double y0,k,x0;
        double[] baselineInfo = getStraightBaseLine(x, y, baseLineDots);
        y0 = baselineInfo[0];
        k = baselineInfo[1];
        x0 = baselineInfo[2];

        double result = x[0];
        double yMax  = 0;
        for (int i = mean/2;i<(x.length<y.length?x.length:y.length) - mean/2;i++){
            double wind = y[i] - (y0 + k * (x[i] - x0));
            for (int j=1;j < mean/2;j++){
                wind += y[i-j]  - (y0 + k * (x[i-j] - x0)) +
                        y[i+j]  - (y0 + k * (x[i+j] - x0));
            }

            if (wind > yMax){
                yMax = wind;
                result = x[i];
            }
        }
        return result;
    }

    public static double[] toArray(List<Double> ly){
        double[] result = new double[ly.size()];
        for (int i=0;i<ly.size();i++){
            result[i] = ly.get(i);
        }
        return result;
    }

    @Deprecated
    public static double[][] integrateWithOutBaseLine(double[] x, double[] y, int baseLineDots){
        double y0,k,x0;
        double[] baselineInfo = getStraightBaseLine(x, y, baseLineDots);
        y0 = baselineInfo[0];
        k = baselineInfo[1];
        x0 = baselineInfo[2];

        double[][] data = new double[3][x.length];
        data[0][0] = x[0];
        data[1][0] = y[0] - y0;
        data[2][0] = 0;
        for (int i=1;i<x.length;i++){
            data[0][i] = x[i];
            data[1][i] = y[i] - (y0 + k * (x[i] - x0));
            data[2][i] = data[2][i-1] + ((data[1][i]+data[1][i-1])/2)*(data[0][i]-data[0][i-1]);
        }
        return data;
    }

    public static double[][] normIntegrateWithOutBaseLine(double[] x, double[] y, double start, double end, int baseLineDots){

        int subSetInfo[] = getSubsetInfo(x, start, end);

        double[] baselineInfo = getStraightBaseLine(x, y, subSetInfo , baseLineDots);
        y = extractStraightLine(x, y, baselineInfo, subSetInfo);
        y = getNormalized(y);

        double[][] data = new double[3][subSetInfo[0]];
        data[0][0] = x[subSetInfo[1]];
        data[1][0] = y[subSetInfo[1]];
        data[2][0] = 0;
        for (int i=1;i<subSetInfo[0];i++){
            data[0][i] = x[i + subSetInfo[1]];
            data[1][i] = y[i];
            data[2][i] = data[2][i-1] + ((data[1][i] + data[1][i-1])/2)*(data[0][i]-data[0][i-1]);
        }
        return data;
    }

    public static double[][] integrateWithOutBaseLine(double[] x, double[] y, double start, double end, int baseLineDots){

        int subSetInfo[] = getSubsetInfo(x, start, end);

        double y0,k,x0;
        double[] baselineInfo = getStraightBaseLine(x, y, subSetInfo , baseLineDots);
        y0 = baselineInfo[0];
        k = baselineInfo[1];
        x0 = baselineInfo[2];

        double[][] data = new double[3][subSetInfo[0] + 1];
        data[0][0] = x[subSetInfo[1]];
        data[1][0] = y[subSetInfo[1]] - y0;
        data[2][0] = 0;
        int j = 1;
        for (int i=1;i<x.length;i++){
            if (x[i] >= start && x[i] <= end){
                data[0][j] = x[i];
                data[1][j] = y[i] - (y0 + k * (x[i] - x0));
                data[2][j] = data[2][j-1] + ((data[1][j] + data[1][j-1])/2)*(data[0][j]-data[0][j-1]);
                j++;
            }
        }
        return data;
    }

    public static double getIntegral(double[] x, double[] y){

        double integral = 0;
        for (int i=1;i<x.length;i++){
            integral = integral + ((y[i] + y[i-1]) /  2) * (x[i]-x[i-1]);
        }
        return integral;
    }

    public static double getNormalizedIntegral(double[] x, double[] y){
        double max = getMax(y);

        double integral = 0;
        for (int i=1;i<x.length;i++){
            integral = integral + ((y[i] + y[i-1]) /  2) * (x[i]-x[i-1]);
        }
        return integral / max;
    }

    public static double[] extractStraightLine(double[] x, double[] y, double[] baselineInfo, int subSetInfo[]){
        double[] result = new double[subSetInfo[0]];
        double y0,k,x0;
        y0 = baselineInfo[0];
        k = baselineInfo[1];
        x0 = baselineInfo[2];
        for (int i=0;i<subSetInfo[0];i++){
            result[i] = y[i+subSetInfo[1]] - (y0 + k * (x[i+subSetInfo[1]] - x0));
        }
        return result;
    }

    public static double[] getNormalized(double[] y){
        double max = getMax(y);

        double[] result = new double[y.length];
        for (int i=1;i<y.length;i++){
            result[i] = y[i] / max;
        }
        return result;
    }

    public static double[] getNormalized(double[] y, int subSetInfo[]){
        double max = getMax(y);

        double[] result = new double[subSetInfo[0]];
        for (int i=subSetInfo[1];i<subSetInfo[0];i++){
            result[i-subSetInfo[0]] = y[i] / max;
        }
        return result;
    }

    public static double[] getStraightBaseLine(double[] x, double[] y, int baseLineDots){
        //important!!!
        if (baseLineDots > x.length/6){
            baseLineDots = x.length/6;
        }
        double y0 = 0,x0 = 0,y1 = 0,x1 = 0;

        for (int i=0;i<baseLineDots;i++){
            x0+=x[i];
            y0+=y[i];
            x1+=x[x.length-1-i];
            y1+=y[y.length-1-i];
        }
        x0 = x0/baseLineDots;
        y0 = y0/baseLineDots;
        x1 = x1/baseLineDots;
        y1 = y1/baseLineDots;
        // y = y0 + (x-x0)*(y1-y0)/(x1-x0)
        double k = (y1-y0)/(x1-x0);
        return new double[]{y0, k, x0};
    }

    public static double[] getStraightBaseLine(
            double[] x, double[] y,
            int[] subSetInfo,
            int baseLineDots){
        //important!!!
        if (baseLineDots > subSetInfo[0]/6){
            baseLineDots = subSetInfo[0]/6;
        }
        double y0 = 0,x0 = 0,y1 = 0,x1 = 0;

        for (int i=0;i<baseLineDots;i++){
            x0+=x[i + subSetInfo[1]];
            y0+=y[i + subSetInfo[1]];
            x1+=x[subSetInfo[2]-1-i];
            y1+=y[subSetInfo[2]-1-i];
        }
        x0 = x0/baseLineDots;
        y0 = y0/baseLineDots;
        x1 = x1/baseLineDots;
        y1 = y1/baseLineDots;
        // y = y0 + (x-x0)*(y1-y0)/(x1-x0)
        double k = (y1-y0)/(x1-x0);
        return new double[]{y0, k, x0};
    }

    public static int[] getSubsetInfo(double[] x, double start, double end){
        int[] result = {0,-1,-1};

        for (int i=0; i<x.length; i++){
            double d = x[i];
            if (d >=start && d <=end)  {
                result[0] = result[0] + 1;
                result[1] = result[1] < 0 ? i : result[1];
                result[2] = i;
            }
        }
        return result;
    }
}
